package com.shxzz.easy12306.services.ticket.service.handler.select;

import cn.hutool.core.collection.CollUtil;
import cn.hutool.core.text.CharSequenceUtil;
import com.shxzz.easy12306.framework.starter.cache.StringRedisTemplateProxy;
import com.shxzz.easy12306.framework.starter.cache.toolkit.CacheUtil;
import com.shxzz.easy12306.framework.starter.common.dto.user.PassengerActualRespDTO;
import com.shxzz.easy12306.framework.starter.convention.exceptions.RemoteException;
import com.shxzz.easy12306.framework.starter.convention.exceptions.ServiceException;
import com.shxzz.easy12306.framework.starter.convention.result.Result;
import com.shxzz.easy12306.framework.starter.designpattern.strategy.AbstractStrategyChoose;
import com.shxzz.easy12306.framework.starter.user.core.UserContext;
import com.shxzz.easy12306.services.ticket.common.constant.RedisKeyConstant;
import com.shxzz.easy12306.services.ticket.common.enums.VehicleSeatTypeEnum;
import com.shxzz.easy12306.services.ticket.common.enums.VehicleTypeEnum;
import com.shxzz.easy12306.services.ticket.config.TicketRemainCacheUpdateProperties;
import com.shxzz.easy12306.services.ticket.dao.entity.TrainStationPriceDO;
import com.shxzz.easy12306.services.ticket.dto.domain.PurchaseTicketPassengerDetailDTO;
import com.shxzz.easy12306.services.ticket.dto.domain.RouteDTO;
import com.shxzz.easy12306.services.ticket.dto.req.PurchaseTicketReqDTO;
import com.shxzz.easy12306.services.ticket.remote.UserRemoteService;
import com.shxzz.easy12306.services.ticket.service.SeatService;
import com.shxzz.easy12306.services.ticket.service.TrainStationPriceService;
import com.shxzz.easy12306.services.ticket.service.TrainStationService;
import com.shxzz.easy12306.services.ticket.service.handler.dto.SeatSelectReqDTO;
import com.shxzz.easy12306.services.ticket.service.handler.dto.SeatSelectRespDTO;
import lombok.RequiredArgsConstructor;
import lombok.extern.slf4j.Slf4j;
import org.springframework.data.redis.core.RedisCallback;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Component;

import java.util.ArrayList;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.CopyOnWriteArrayList;
import java.util.concurrent.Future;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.stream.Collectors;

/**
 * 购票座位选择器
 *
 * @author shxzz
 * @create 2024-03-07-20:26
 */
@Slf4j
@RequiredArgsConstructor
@Component
public class SeatSelector {

    private final TicketRemainCacheUpdateProperties ticketRemainCacheUpdateProperties;

    private final ThreadPoolExecutor threadPoolExecutor;
    private final AbstractStrategyChoose abstractStrategyChoose;

    private final StringRedisTemplateProxy stringRedisTemplateProxy;

    private final TrainStationService trainStationService;
    private final SeatService seatService;
    private final TrainStationPriceService trainStationPriceService;

    private final UserRemoteService userRemoteService;

    public List<SeatSelectRespDTO> selectAndLockSeat(Integer trainType, PurchaseTicketReqDTO requestParam) {
        Map<Integer, List<PurchaseTicketPassengerDetailDTO>> passengerMapSplitBySeatType = requestParam.getPassengers().stream()
                .collect(Collectors.groupingBy(PurchaseTicketPassengerDetailDTO::getSeatType));
        List<SeatSelectRespDTO> seatSelectRespDTOlist = new CopyOnWriteArrayList<>();
        if (passengerMapSplitBySeatType.size() > 1) {
            List<Future<List<SeatSelectRespDTO>>> futureList = new ArrayList<>();
            passengerMapSplitBySeatType.forEach((seatType, passengerDetailDTOList) -> {
                Future<List<SeatSelectRespDTO>> future = threadPoolExecutor.submit(() -> selectSeat(trainType, seatType, requestParam, passengerDetailDTOList));
                futureList.add(future);
            });
            futureList.parallelStream().forEach(each -> {
                try {
                    seatSelectRespDTOlist.addAll(each.get());
                } catch (Exception e) {
                    throw new ServiceException("余票不足");
                }
            });
        } else {
            passengerMapSplitBySeatType.forEach((key, value) -> seatSelectRespDTOlist.addAll(selectSeat(trainType, key, requestParam, value)));
        }

        Result<List<PassengerActualRespDTO>> passengerInfoQueryResult = userRemoteService.listPassengerQueryByIds(
                UserContext.getUsername(),
                seatSelectRespDTOlist.stream().map(SeatSelectRespDTO::getPassengerId).map(Long::parseLong).toList());
        List<PassengerActualRespDTO> passengerInfoList = passengerInfoQueryResult.getData();
        if (!passengerInfoQueryResult.isSuccess() || CollUtil.isEmpty(passengerInfoList)) {
            log.error("乘车人信息远程查询失败，查询用户名[{}]", UserContext.getUsername());
            throw new RemoteException("远程调用乘车人信息异常");
        }
        String trainId = requestParam.getTrainId();
        String departure = requestParam.getDeparture();
        String arrival = requestParam.getArrival();
        List<TrainStationPriceDO> trainStationPrice = trainStationPriceService.getTrainStationPrice(trainId, departure, arrival);
        seatSelectRespDTOlist.forEach(each -> {
            passengerInfoList.stream()
                    .filter(passenger -> CharSequenceUtil.equals(passenger.getId().toString(), each.getPassengerId()))
                    .findFirst()
                    .ifPresent(passenger -> {
                        each.setRealName(passenger.getRealName());
                        each.setPhone(passenger.getPhone());
                        each.setIdType(passenger.getIdType());
                        each.setIdCard(passenger.getIdCard());
                        each.setUserType(passenger.getDiscountType());
                    });
            trainStationPrice.stream()
                    .filter(seat -> Objects.equals(seat.getSeatType(), each.getSeatType()))
                    .findFirst()
                    .ifPresent(trainStationPriceDO -> each.setAmount(trainStationPriceDO.getPrice()));
        });
        // 扣减缓存，LOCK 座位
        List<RouteDTO> routeDTOList = trainStationService.listTakeoutTrainStationRoute(trainId, departure, arrival);
        if(!CharSequenceUtil.equals(ticketRemainCacheUpdateProperties.getTicketUpdateType(), TicketRemainCacheUpdateProperties.BINLOG_UPDATE_TYPE)) {
            passengerMapSplitBySeatType.forEach((seatType, passengerDetailDTOList) ->
                    decrementTicketCache(trainId, seatType, routeDTOList, passengerDetailDTOList.size()));
        }
        seatService.lockSeat(trainId, seatSelectRespDTOlist, routeDTOList);
        return seatSelectRespDTOlist;
    }

    /**
     * 扣减受影响区间的余票缓存
     */
    private void decrementTicketCache(String trainId, Integer seatType, List<RouteDTO> routeDTOList, int size) {
        StringRedisTemplate stringRedisTemplate = stringRedisTemplateProxy.getInstance();
        List<Object> remainTickets = stringRedisTemplate.executePipelined((RedisCallback<String>) connection -> {
            routeDTOList.forEach(routeDTO -> {
                String key = CacheUtil.buildKey(RedisKeyConstant.TRAIN_REMAIN_TICKET_PREFIX, trainId, routeDTO.getStartStation(), routeDTO.getEndStation());
                connection.hashCommands().hIncrBy(key.getBytes(), seatType.toString().getBytes(), -size);
            });
            return null;
        });
        remainTickets.forEach(remainTicket -> {
            if (remainTicket == null || Integer.parseInt(remainTicket.toString()) < 0) {
                log.error("余票缓存扣减异常，异常列车:[{}]", trainId);
                throw new ServiceException("余票不足");
            }
        });
    }


    /**
     * 构建策略分发选座
     */
    private List<SeatSelectRespDTO> selectSeat(Integer trainType, Integer seatType, PurchaseTicketReqDTO requestParam,
                                               List<PurchaseTicketPassengerDetailDTO> passengerSeatDetails) {
        SeatSelectReqDTO seatSelectReqDTO = SeatSelectReqDTO.builder()
                .trainId(requestParam.getTrainId())
                .trainType(trainType)
                .seatType(seatType)
                .passengerSeatDetails(passengerSeatDetails)
                .chooseSeats(requestParam.getChooseSeats())
                .departure(requestParam.getDeparture())
                .arrival(requestParam.getArrival())
                .build();
        String strategyKey = VehicleTypeEnum.getNameByCode(trainType) + VehicleSeatTypeEnum.getNameByCode(seatType);

        try {
            return abstractStrategyChoose.chooseAndExecuteResp(strategyKey, seatSelectReqDTO);
        } catch (Exception e) {
            throw new ServiceException("当前车次类型列车暂未适配，请选择高铁车次");
        }
    }

}
